#include "GeometryGenerator.h"
#include "../Math/MathHelper.h"

void GeometryGenerator::CreatePlanePT(float _width, float _height, MeshDataPT& _mesh)
{
	float width = _width * 0.5f;
	float height = _height * 0.5f;

	VertexPT verts[4];

	verts[0] = VertexPT( -width,	height,		0.0f, 0.0f, 0.0f);
	verts[1] = VertexPT( width,		height,		0.0f, 1.0f, 0.0f);
	verts[2] = VertexPT( width,		-height,	0.0f, 1.0f, 1.0f);
	verts[3] = VertexPT( -width,	-height,	0.0f, 0.0f, 1.0f);

	_mesh.vertices.assign(&verts[0], &verts[4]);

	unsigned int i[6] =
	{
		0,1,2,
		0,2,3,
	};

	_mesh.indices.assign(&i[0], &i[6]);
}

//Create a box with Position | Normal | Texture information
void GeometryGenerator::CreateBoxPNT(float _width, float _height, float _depth, MeshDataPNT& _mesh)
{

	VertexPNT verts[24];

	float width = _width * 0.5f;
	float height = _height * 0.5f;
	float depth = _depth * 0.5f;

	// Fill in the front face vertex data.
	verts[0] = VertexPNT(-width, -height, -depth, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f);
	verts[1] = VertexPNT(-width, +height, -depth, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f);
	verts[2] = VertexPNT(+width, +height, -depth, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f);
	verts[3] = VertexPNT(+width, -height, -depth, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f);

	// Fill in the back face vertex data.
	verts[4] = VertexPNT(-width, -height, +depth, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f);
	verts[5] = VertexPNT(+width, -height, +depth, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f);
	verts[6] = VertexPNT(+width, +height, +depth, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f);
	verts[7] = VertexPNT(-width, +height, +depth, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f);

	// Fill in the top face vertex data.
	verts[8]  = VertexPNT(-width, +height, -depth, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f);
	verts[9]  = VertexPNT(-width, +height, +depth, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f);
	verts[10] = VertexPNT(+width, +height, +depth, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f);
	verts[11] = VertexPNT(+width, +height, -depth, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f);

	// Fill in the bottom face vertex data.
	verts[12] = VertexPNT(-width, -height, -depth, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f);
	verts[13] = VertexPNT(+width, -height, -depth, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f);
	verts[14] = VertexPNT(+width, -height, +depth, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f);
	verts[15] = VertexPNT(-width, -height, +depth, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f);

	// Fill in the left face vertex data.
	verts[16] = VertexPNT(-width, -height, +depth, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f);
	verts[17] = VertexPNT(-width, +height, +depth, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f);
	verts[18] = VertexPNT(-width, +height, -depth, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f);
	verts[19] = VertexPNT(-width, -height, -depth, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f);

	// Fill in the right face vertex data.
	verts[20] = VertexPNT(+width, -height, -depth, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f);
	verts[21] = VertexPNT(+width, +height, -depth, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f);
	verts[22] = VertexPNT(+width, +height, +depth, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f);
	verts[23] = VertexPNT(+width, -height, +depth, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f);

	_mesh.vertices.assign(&verts[0], &verts[24]);

	// Create the indices.
	unsigned int i[36];

	// Fill in the front face index data
	i[0] = 0; i[1] = 1; i[2] = 2;
	i[3] = 0; i[4] = 2; i[5] = 3;

	// Fill in the back face index data
	i[6] = 4; i[7]  = 5; i[8]  = 6;
	i[9] = 4; i[10] = 6; i[11] = 7;

	// Fill in the top face index data
	i[12] = 8; i[13] =  9; i[14] = 10;
	i[15] = 8; i[16] = 10; i[17] = 11;

	// Fill in the bottom face index data
	i[18] = 12; i[19] = 13; i[20] = 14;
	i[21] = 12; i[22] = 14; i[23] = 15;

	// Fill in the left face index data
	i[24] = 16; i[25] = 17; i[26] = 18;
	i[27] = 16; i[28] = 18; i[29] = 19;

	// Fill in the right face index data
	i[30] = 20; i[31] = 21; i[32] = 22;
	i[33] = 20; i[34] = 22; i[35] = 23;

	_mesh.indices.assign(&i[0], &i[36]);
}

void GeometryGenerator::CreateGeospherePNT(float radius, UINT numSubdivisions, MeshDataPNT& meshData)
{
	// Put a cap on the number of subdivisions.
	numSubdivisions = MathHelper::Min(numSubdivisions, 5u);

	// Approximate a sphere by tessellating an icosahedron.

	const float X = 0.525731f; 
	const float Z = 0.850651f;

	XMFLOAT3 pos[12] = 
	{
		XMFLOAT3(-X, 0.0f, Z),  XMFLOAT3(X, 0.0f, Z),  
		XMFLOAT3(-X, 0.0f, -Z), XMFLOAT3(X, 0.0f, -Z),    
		XMFLOAT3(0.0f, Z, X),   XMFLOAT3(0.0f, Z, -X), 
		XMFLOAT3(0.0f, -Z, X),  XMFLOAT3(0.0f, -Z, -X),    
		XMFLOAT3(Z, X, 0.0f),   XMFLOAT3(-Z, X, 0.0f), 
		XMFLOAT3(Z, -X, 0.0f),  XMFLOAT3(-Z, -X, 0.0f)
	};

	DWORD k[60] = 
	{
		1,4,0,  4,9,0,  4,5,9,  8,5,4,  1,8,4,    
		1,10,8, 10,3,8, 8,3,5,  3,2,5,  3,7,2,    
		3,10,7, 10,6,7, 6,11,7, 6,0,11, 6,1,0, 
		10,1,6, 11,0,9, 2,11,9, 5,2,9,  11,2,7 
	};

	meshData.vertices.resize(12);
	meshData.indices.resize(60);

	for(UINT i = 0; i < 12; ++i)
		meshData.vertices[i].position = pos[i];

	for(UINT i = 0; i < 60; ++i)
		meshData.indices[i] = k[i];

	for(UINT i = 0; i < numSubdivisions; ++i)
		Subdivide(meshData);

	// Project vertices onto sphere and scale.
	for(UINT i = 0; i < meshData.vertices.size(); ++i)
	{
		// Project onto unit sphere.
		XMVECTOR n = XMVector3Normalize(XMLoadFloat3(&meshData.vertices[i].position));

		// Project onto sphere.
		XMVECTOR p = radius*n;

		XMStoreFloat3(&meshData.vertices[i].position, p);
		XMStoreFloat3(&meshData.vertices[i].normal, n);

		// Derive texture coordinates from spherical coordinates.
		float theta = AngleFromXY(
			meshData.vertices[i].position.x, 
			meshData.vertices[i].position.z);

		float phi = acosf(meshData.vertices[i].position.y / radius);

		meshData.vertices[i].texCoord.x = theta/XM_2PI;
		meshData.vertices[i].texCoord.y = phi/XM_PI;
	}
}

void GeometryGenerator::Subdivide(MeshDataPNT& meshData)
{
	// Save a copy of the input geometry.
	MeshDataPNT inputCopy = meshData;


	meshData.vertices.resize(0);
	meshData.indices.resize(0);

	//       v1
	//       *
	//      / \
	//     /   \
	//  m0*-----*m1
	//   / \   / \
	//  /   \ /   \
	// *-----*-----*
	// v0    m2     v2

	UINT numTris = inputCopy.indices.size()/3;
	for(UINT i = 0; i < numTris; ++i)
	{
		VertexPNT v0 = inputCopy.vertices[ inputCopy.indices[i*3+0] ];
		VertexPNT v1 = inputCopy.vertices[ inputCopy.indices[i*3+1] ];
		VertexPNT v2 = inputCopy.vertices[ inputCopy.indices[i*3+2] ];

		//
		// Generate the midpoints.
		//

		VertexPNT m0, m1, m2;

		// For subdivision, we just care about the position component.  We derive the other
		// vertex components in CreateGeosphere.

		m0.position = XMFLOAT3(
			0.5f*(v0.position.x + v1.position.x),
			0.5f*(v0.position.y + v1.position.y),
			0.5f*(v0.position.z + v1.position.z));

		m1.position = XMFLOAT3(
			0.5f*(v1.position.x + v2.position.x),
			0.5f*(v1.position.y + v2.position.y),
			0.5f*(v1.position.z + v2.position.z));

		m2.position = XMFLOAT3(
			0.5f*(v0.position.x + v2.position.x),
			0.5f*(v0.position.y + v2.position.y),
			0.5f*(v0.position.z + v2.position.z));

		//
		// Add new geometry.
		//

		meshData.vertices.push_back(v0); // 0
		meshData.vertices.push_back(v1); // 1
		meshData.vertices.push_back(v2); // 2
		meshData.vertices.push_back(m0); // 3
		meshData.vertices.push_back(m1); // 4
		meshData.vertices.push_back(m2); // 5
 
		meshData.indices.push_back(i*6+0);
		meshData.indices.push_back(i*6+3);
		meshData.indices.push_back(i*6+5);

		meshData.indices.push_back(i*6+3);
		meshData.indices.push_back(i*6+4);
		meshData.indices.push_back(i*6+5);

		meshData.indices.push_back(i*6+5);
		meshData.indices.push_back(i*6+4);
		meshData.indices.push_back(i*6+2);

		meshData.indices.push_back(i*6+3);
		meshData.indices.push_back(i*6+1);
		meshData.indices.push_back(i*6+4);
	}
}

float GeometryGenerator::AngleFromXY(float x, float y)
{
	float theta = 0.0f;
 
	// Quadrant I or IV
	if(x >= 0.0f) 
	{
		// If x = 0, then atanf(y/x) = +pi/2 if y > 0
		//                atanf(y/x) = -pi/2 if y < 0
		theta = atanf(y / x); // in [-pi/2, +pi/2]

		if(theta < 0.0f)
			theta += 2.0f*3.1415926535f; // in [0, 2*pi).
	}

	// Quadrant II or III
	else      
		theta = atanf(y/x) + 3.1415926535f; // in [0, 2*pi).

	return theta;
}